package fhq.hcmute.qrcode.common;

/**
 * @author: Hien Nguyen Le
 * @Email: nglehien@gmail.com
 */
class OtsuThresholder {
	private int width;
	private int height;
	private int threshold;
	protected final int[][] matrix;

	public OtsuThresholder(int width, int height, byte[] srcData) {
		this.width = width;
		this.height = height;
		matrix = new int[height][width];

		int[] histData = calHistogram(srcData);
		runOtsuThreshold(histData, srcData);
	}

	private void runOtsuThreshold(int[] histData, byte[] srcData) {
		// Total number of pixels
		int total = srcData.length;

		float sum = 0;
		for (int t = 0; t < 256; t++)
			sum += t * histData[t];

		float sumB = 0;
		int wB = 0;
		int wF = 0;

		float varMax = 0;
		for (int t = 0; t < 256; t++) {
			wB += histData[t]; // Weight Background
			if (wB == 0)
				continue;

			wF = total - wB; // Weight Foreground
			if (wF == 0)
				break;

			sumB += (float) (t * histData[t]);

			float mB = sumB / wB; // Mean Background
			float mF = (sum - sumB) / wF; // Mean Foreground

			// Calculate Between Class Variance
			float varBetween = (float) wB * (float) wF * (mB - mF) * (mB - mF);

			// Check if new maximum found
			if (varBetween > varMax) {
				varMax = varBetween;
				this.threshold = t;
				if (this.threshold == 0) {
					this.threshold = 1;
				}
			}
		}
		for (int i = 0, offset = i * width; i < height; i++, offset += width) {
			for (int j = 0; j < width; j++) {
				int value = ((0xFF & srcData[offset + j]) <= threshold) ? BinaryImage.BLACK
						: BinaryImage.WHITE;
				matrix[i][j] = value;
			}
		}

	}

	public int[][] getMatrix() {
		return this.matrix;
	}

	private int[] calHistogram(byte[] srcData) {
		int[] h = new int[256];
		int ptr = 0;
		while (ptr < srcData.length) {
			int value = 0xFF & srcData[ptr];
			h[value]++;
			ptr++;
		}
		return h;
	}
}
